#include <string>
#include <errno.h>
#include <malloc_align.h>
#include <ProcessComposition.h>
#include <ProcessStats.h>
#include <CSPConfig.h>
#include <csp_shared_types.h>
#include <csp_misc.h>
#include <csp_protocol_msg.h>
#include <shared_data.h>
#include <csp_debug.h>
#include <Scheduler.h>
#include <SpuProgram.h>
#include <libmisc.h>
#include <Event.h>
#include <unistd.h>
#include <ppu_intrinsics.h>
#include <Alting.h>
#include <PCBCache.h>

using namespace std;

ProcessComposition::ProcessComposition(string mnemonic, 
		Event *event, ProcessComposition *parent)
{
	pcb = NULL;

	char *cstr = (char*) malloc(mnemonic.size()+1);
	strcpy(cstr, mnemonic.c_str());
	Init(djb2_hash(cstr), event, parent);

	free(cstr); 
}

ProcessComposition::ProcessComposition(uint64_t mnemonic_hash, 
		Event *event, ProcessComposition *parent)
{
	pcb = NULL;
	Init(mnemonic_hash, event, parent);
}


/* Classic trick when the constructor needs to be overloaded.
 * */
	void
ProcessComposition::Init(uint64_t mnemonic_hash, Event *event_, ProcessComposition *parent_)
{
	extern CSPConfig* configuration;
/*
	if( !pcb ){
		pcb = (pcb_t*) _malloc_align(sizeof(pcb_t), 7);
	}
*/
	PCBCache *pcbcache = PCBCache::Instance();	
	pcbIdx = pcbcache->GetFreeIdx();
	pcb = &(pcbcache->Head())[pcbIdx];

	spu_prgm = configuration->GetProgram(mnemonic_hash);

	pcb->state = STATE_INIT;
	pcb->pid = -1;
	//pcb->dma_completion_flag = (ea_addr_t) _malloc_align(sizeof(qword),7);
	last_spe_id = -1;

	event = event_;
	parent = parent_;
/*
	hist_idx = 0;
*/
	alt = NULL;
	next = NULL;
}


void ProcessComposition::SetEvent(Event* event_, ProcessComposition* parent_)
{
	event = event_;
	parent = parent_;
}

/* Setup program state and load appriopiate code with information we didn't have when
 * the object was initialized.
 * */
	void 
ProcessComposition::Setup(unsigned char ls_mem_slot_, uint32_t pid)
{
	ea_addr_t eastore;

	unsigned char ls_mem_slot = spu_prgm->next_slot < NUMTHREADS ? 0 : 1;
	spu_prgm->next_slot =  (spu_prgm->next_slot+1) == NUMTHREADS*2 ? 0 :(spu_prgm->next_slot+1) ;

	ASSERT(spu_prgm);
	spu_prgm->CopyToProcess(pcb, ls_mem_slot);
	pcb->state = STATE_INIT;
	pcb->pid = pid;
	spid = pid;
	pcb->op_ret_val = 0;
	pcb->ret_val = 0;
	pcb->slot = ls_mem_slot;
	eastore = SAVE_PROC_AREA_SIZE( (*pcb) );
	pcb->ea_store = (ea_addr_t) _malloc_align( 
			eastore , 7);
	pcb->dma_completion_flag[0] = PROCESS_DMA_COMPLETION;
	pcb->dma_completion_flag[1] = PROCESS_DMA_COMPLETION;
	pcb->dma_completion_flag_ea_addr = (ea_addr_t) &pcb->dma_completion_flag[0];
	pcb->pcb_ea = (ea_addr_t) pcb;
	pcb->channel_data = 0;


	//printf("Process based on'%s' got slot=%i\n", spu_prgm->GetMnemonic(), pcb->slot);
}



void ProcessComposition::SetExitStatus(int32_t status)
{
	pcb->op_ret_val = status;
	pcb->ret_val = status;
}



void ProcessComposition::Print()
{
	printf("pcb->stack_start=%x, pcb->bss_size=%x, pcb->exec=%lx, "
			"pcb->exec_size=%x, pcb->vaddr=%x, pcb->entry=%x, "
			"pcb->slot=%i, pcb->ea_store=%lx, pcb->stack_size=%x, "
			"pcb->pid=%i, pcb->state=%x, pcb->ret_val=%i, "
			"pcb->dma_completion_flag=%x, pcb->channel_data=%lx, "
			"pcb->pcb_ea=%lx\n",
			pcb->stack_start, pcb->bss_size, pcb->exec, 
			pcb->exec_size, pcb->vaddr, pcb->entry, 
			pcb->slot, pcb->ea_store, pcb->stack_size, 
			pcb->pid, pcb->state, pcb->ret_val,
			pcb->dma_completion_flag, pcb->channel_data, 
			pcb->pcb_ea);
	ASSERT( spid == pcb->pid || pcb->pcb_ea == (ea_addr_t) pcb);
}

	void 
ProcessComposition::ClearDMACompletionFlag()
{
	//ASSERT(pcb->dma_completion_flag);
	//pcb->dma_completion_flag = 0;
	volatile unsigned char *flag1 = &(pcb->dma_completion_flag[0]); 
	volatile unsigned char *flag2 = &(pcb->dma_completion_flag[1]); 
	*flag1 = 0;
	*flag2 = 0;

	ASSERT( spid == pcb->pid || pcb->pcb_ea == (ea_addr_t) pcb);
}

	void 
ProcessComposition::WaitForDMACompletion(int pos, int spe_id)
{
	/* Spin-lock */
	bool t = false;
	volatile unsigned char *flag1 = &(pcb->dma_completion_flag[0]); 
	volatile unsigned char *flag2 = &(pcb->dma_completion_flag[1]); 
	while(PROCESS_DMA_COMPLETION != *flag1 && PROCESS_DMA_COMPLETION != *flag2  ){ 
		; /* Wait  */
		//usleep(50);
		//printf("Waiting for DMA-completion (spe_id=%i, state=%x, pid=%i, pos=%i).\n", 
		//		spe_id, pcb->state, pcb->pid, pos);
		//t = true;
	}
	//if(t){
	//	printf("finished waiting for DMA (state=%x, pos=%i) .\n", pcb->state, pos);
	//}

	//__lwsync();

	ASSERT( spid == pcb->pid || pcb->pcb_ea == (ea_addr_t) pcb);
}


	void 
ProcessComposition::SetState(uint32_t state)
{
	pcb->state = state;
}

	unsigned char
ProcessComposition::GetMemorySlot()
{
	return pcb->slot;

	ASSERT( spid == pcb->pid || pcb->pcb_ea == (ea_addr_t) pcb);
}

	void 
ProcessComposition::SetMemorySlot(unsigned char slot)
{
	pcb->slot = slot;
}

	uint32_t
ProcessComposition::GetState()
{
	return pcb->state;

	ASSERT( spid == pcb->pid || pcb->pcb_ea == (ea_addr_t) pcb);
}

/*
   void 
   ProcessComposition::SetProcessData(pcb_t &_process_data)
   {
   if(NULL == memcpy(&pcb, &_process_data, sizeof(pcb_t))){
   perror("Could not update process data.");	
   }
   }

   void 
   ProcessComposition::GetProcessData(pcb_t *_process_data)
   {
   if(NULL == memcpy(_process_data, &pcb, sizeof(pcb_t))){
   perror("Could not update process data.");	
   }
   }
   */

	void 
ProcessComposition::Schedule()
{
	Scheduler &scheduler = *Scheduler::Instance();	
/*
	this->SetLast(4, -1);
*/
	scheduler.put(*this, pcb->state);
}

	void 
ProcessComposition::Add(ProcessComposition &component)
{

}

	void 
ProcessComposition::TriggerEvent(int status)
{
	/* If we don't have an event - then trigger parent's event. */
	if( parent ){
		parent->TriggerEvent(status);	
	}else{
		event->Trigger(status);
	}
}


Alting *ProcessComposition::GetAlting(csp_channel_t* list, int size)
{
	if( !alt ){
		alt = new Alting(this);
	}	

	alt->Setup(list, size);

	return alt;
}
